#include "apr.h"
#include "apr_network_io.h"
#include "apr_pools.h"
#include "apr_thread_proc.h"
#include "apr_thread_pool.h"
#include <string.h>
#include <stdio.h>
#include "apr_md5.h"
#include "apr_random.h"
#include "apr_thread_mutex.h"
#include "apr_thread_cond.h"

#define SERVER_PORT 8888
#define CLIENT_PORT 7777
#define CLIENT_PORT6 64
#define INIT_THREADS 3
#define MAX_THREADS 1000
#define CONNECT_CLIENT 25

#define BMGPORT 1024

#define MAX_BUFF_LEN 4100

#define CLIENT_IP  "58.196.13.55"
#define SERVER_IP  "2001:256:100:649::2"

#define RIGHT 0
#define WRONG 0

apr_sockaddr_t *server_addr, *my_addr;

static apr_int32_t REQUEST_CNT = 0;

static apr_byte_t req[5] = {0x16, 0x00, 0x34, 0x93};
//static apr_byte_t req[5] = {0x16, 0x00, 0x00, 0x00};
static apr_byte_t tmp_buff[4100] = {0x16, 0x02, 0x34, 0x93 };
static apr_byte_t wrong_buff[4100] = {0x16, 0x02, 0x34, 0x93 };

//static apr_byte_t *user_id = "02144111001\0\0\0\0\0";
//static apr_byte_t *bmg_id = "BMG00012407A82C";
//static apr_byte_t mac[6] = {0x00, 0x12, 0x40, 0x70, 0xA8, 0x2C};

static apr_byte_t *user_id = "02144402074";
static apr_byte_t mac[6] = {0x00, 0x12, 0x40, 0x70, 0x97, 0x88};
static apr_byte_t *bmg_id = "BMG000124070978e";

//static apr_byte_t *user_id = "0210000000000016";
//static apr_byte_t *user_id = "";
//static apr_byte_t *bmg_id =  "BMG0001240700f38";
//static apr_byte_t mac[6] = {0x00, 0x12, 0x40, 0x70, 0x0f, 0x38};
//static apr_byte_t *user_id = "0210060001010208";
//static apr_byte_t *bmg_id = "BMG0000000000103";
//static apr_byte_t mac[6] = {0x00, 0x00, 0x00, 0x00, 0x01, 0x03};

static apr_thread_cond_t *cond, *req_cond, *print_cond;
static apr_thread_mutex_t *mutex, *req_mutex, *print_mutex;

static apr_int32_t right_sent = 0;
static apr_int32_t wrong_sent = 0;
static apr_int32_t right_recv = 0;
static apr_int32_t wrong_recv = 0;
static apr_int32_t auth_timeout = 0;
static apr_int32_t req_timeout = 0;

static apr_int32_t TOTAL = 0;
static apr_interval_time_t TIMEOUT = 100000;

static char *typestr[2] = 
{
    "RIGHT",
    "WRONG"
};

void session_password_encode(const apr_byte_t *nonce, const apr_byte_t *password, apr_byte_t *outbuff)
{
    apr_md5_ctx_t apr_md5c;
    apr_byte_t bi[16];
    //apr_pool_t *p;
    //apr_pool_create(&p, NULL);
    apr_int32_t ss_len = strlen(password) + 17;
    apr_byte_t ss[100];

    ss[0] = nonce[16];
    memcpy(&(ss[1]), password, strlen(password));
    puts(password);
    memcpy(ss+1+strlen(password), &(nonce[0]), 16);

    //sprintf(ss, nonce);

    apr_md5_init( &apr_md5c );
    apr_md5_update( &apr_md5c, ss, ss_len);
    apr_md5_final( bi, &apr_md5c );
    int i;
    for(i = 0; i < 16; i++)
        printf("%02x", bi[i]);
    printf("\n");

    memcpy(outbuff, bi, 16);
}

static void * APR_THREAD_FUNC print_result(apr_thread_t *thd, void *data)
{
    puts("\n**********************Test Result*************************");
    printf("right_sent = %d\n", right_sent);
    printf("wrong_sent = %d\n", wrong_sent);
    printf("right_recv = %d\n", right_recv);
    printf("wrong_recv = %d\n", wrong_recv);
    printf("auth_timeout = %d\n", auth_timeout);
    printf("req_timeout = %d\n", req_timeout);
    puts("**********************Test Result End**********************");
    apr_thread_mutex_lock(print_mutex);
    apr_thread_cond_signal(print_cond);
    apr_thread_mutex_unlock(print_mutex);
}

static void * APR_THREAD_FUNC sub_thread_func(apr_thread_t *thd, void *data)
{
    apr_pool_t *p;
    apr_status_t rv;
    apr_socket_t *sock;
    apr_sockaddr_t *client_addr;
    apr_size_t insize = 10000, outsize = 10000;
    apr_byte_t out_buff[MAX_BUFF_LEN], in_buff[MAX_BUFF_LEN], rand_byte[10];

    apr_int32_t request_cnt;

    request_cnt = REQUEST_CNT;
    apr_thread_mutex_lock(req_mutex);
    apr_thread_cond_signal(req_cond);
    apr_thread_mutex_unlock(req_mutex);

    apr_int32_t auth_type;

    apr_pool_create(&p, NULL);

    int i;
//puts("aaa");
//    printf("in thread@[%d]\n", request_cnt);


    rv = apr_sockaddr_info_get(&client_addr,CLIENT_IP, APR_UNSPEC,CLIENT_PORT+request_cnt, 0, p);
    if(rv != 0)
    {
        puts("2");
    }

    rv = apr_socket_create(&sock,client_addr->family, SOCK_DGRAM,APR_PROTO_UDP,p);
    if(rv != 0)
    {
        puts("3");
    }

    rv = apr_socket_bind(sock,client_addr);
    if(rv != 0)
    {
        apr_psprintf(p, "%pm", &rv);
    }


    outsize = 4;
    rv = apr_socket_sendto(sock,server_addr,0, req, &outsize);
    printf("send req@[%d]\n", request_cnt);
    if(rv != 0)
        puts("5");

    apr_socket_timeout_set(sock, TIMEOUT);
    rv = apr_socket_recvfrom(client_addr,sock,0, in_buff,&insize);
    if(rv != 0)
    {
        req_timeout++;
        printf("recv req response timeout@[%d]\n", request_cnt);
        goto cleanup;
    }

    printf("recv req-response: size=%d@[%d]:\n", insize, request_cnt);
    for(i = 0; i < insize; i++)
        printf("%02x ", in_buff[i]);
    printf("\n");

    memcpy(out_buff, tmp_buff, 58);

    out_buff[2] = in_buff[2];
    out_buff[3] = in_buff[3];

    rv = apr_generate_random_bytes(rand_byte, 1);
    auth_type = rand_byte[0] % 2;
    if(auth_type == RIGHT)
    {
        right_sent++;
        session_password_encode(in_buff+4 , "123456", out_buff+36);
    }
    else
    {
        wrong_sent++;
        session_password_encode(in_buff+4 , "123456", out_buff+36);
    }

    outsize = 58; 
    rv = apr_socket_sendto(sock,server_addr,0, out_buff, &outsize);
    printf("send %s auth buff@[%d]\n", typestr[auth_type], request_cnt);

    insize = 10000;
    apr_socket_timeout_set(sock, TIMEOUT);
    rv = apr_socket_recvfrom(client_addr,sock,0, in_buff,&insize);
    if(rv != 0)
    {
        auth_timeout++;
        printf("recv auth-response timeout@[%d]\n", request_cnt);
        goto cleanup;
    }

    if(in_buff[1] == 0x03)
        right_recv++;
    else if(in_buff[1] == 0x04)
        wrong_recv++;
    printf("recv auth-response: size=%d@[%d]:\n", insize, request_cnt);
    for(i = 0; i < insize; i++)
        printf("%02x ", in_buff[i]);
    for(i = 0; i < insize; i++)
        printf("%c", in_buff[i]);
    printf("\n");

cleanup:
    apr_socket_close(sock);
    apr_pool_destroy(p);
    //apr_thread_mutex_unlock(mutex);
    if(request_cnt == TOTAL-1)
    {
        apr_thread_mutex_lock(mutex);
        apr_thread_cond_signal(cond);
        apr_thread_mutex_unlock(mutex);
    }
    return;
}

void init_buff()
{
    apr_int32_t now = 4;
    memcpy(tmp_buff+now, user_id, 16);
//    memcpy(wrong_buff+now, user_id, 16);
    now += 16;
    memcpy(tmp_buff+now, bmg_id, 16);
//    memcpy(wrong_buff+now, bmg_id, 16);
    now += 16;
    now += 16;
    memcpy(tmp_buff+now, mac, 6);
//    memcpy(wrong_buff+now, mac, 6);
}

void main(apr_int32_t argc, apr_byte_t *argv[])
{

        if(argc < 5)
        {
            puts("usage:IP PORT amount timeout");
            return;
        }
        TIMEOUT = atoi(argv[4]);
        init_buff();
		apr_pool_t *p;
		apr_status_t rv;
	
		apr_initialize();
		atexit(apr_terminate);
		
		apr_pool_create(&p, NULL);
	   
        /*test*/
       // fprintf(stdout,"%d",&p);
       // apr_pool_destroy(p);
        //fprintf(stdout, "%d", &p);
        //apr_pool_destroy(p);
        //return 0;

        rv = apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_DEFAULT, p);
        rv = apr_thread_cond_create(&cond, p);
        rv = apr_thread_mutex_create(&req_mutex, APR_THREAD_MUTEX_DEFAULT, p);
        rv = apr_thread_cond_create(&req_cond, p);
        rv = apr_thread_mutex_create(&print_mutex, APR_THREAD_MUTEX_DEFAULT, p);
        rv = apr_thread_cond_create(&print_cond, p);

	  apr_uint32_t len;
	  apr_byte_t type, length;
//get server address
                rv = apr_sockaddr_info_get(&server_addr,argv[1],APR_UNSPEC,atoi(argv[2]), 0, p);
                if(rv==APR_SUCCESS)
                    puts("apr_sockaddr_info_get SERVER...");
/*
	apr_size_t out_size = 4;
	apr_socket_t *s1;
        rv = apr_sockaddr_info_get(&my_addr, CLIENT_IP, APR_INET, BMGPORT, 0, p);
         apr_socket_create(&s1,my_addr->family, SOCK_DGRAM,APR_PROTO_UDP,p);
	apr_socket_bind(s1, my_addr);
	rv = apr_socket_sendto(sock,server_addr,0, req, &outsize);
   	printf("send req1");
  	if(rv != 0)
            puts("5");

    	apr_socket_timeout_set(sock, TIMEOUT);
    	rv = apr_socket_recvfrom(client_addr,sock,0, req,&out_size);
    	if(rv != 0)
    	{
        	req_timeout++;
        	printf("recv req response timeout@[%d]\n", request_cnt);
    	}
	rv = apr_socket_sendto(sock,server_addr,0, req, &outsize);
   	printf("send req1");
  	if(rv != 0)
            puts("5");

    	apr_socket_timeout_set(sock, TIMEOUT);
    	rv = apr_socket_recvfrom(client_addr,sock,0, req,&out_size);
    	if(rv != 0)
    	{
        	req_timeout++;
        	printf("recv req response timeout@[%d]\n", request_cnt);
    	}
	rv = apr_socket_sendto(sock,server_addr,0, req, &outsize);
   	printf("send req1");
  	if(rv != 0)
            puts("5");

    	apr_socket_timeout_set(sock, TIMEOUT);
    	rv = apr_socket_recvfrom(client_addr,sock,0, req,&out_size);
    	if(rv != 0)
    	{
        	req_timeout++;
        	printf("recv req response timeout@[%d]\n", request_cnt);
    	}
*/

	apr_thread_mutex_lock(req_mutex);
	  //create multi-thread client..... 
	  apr_int32_t i;
          TOTAL = atoi(argv[3]);
		for (i=0;i<TOTAL;++i)
		{
		   apr_thread_t * 	new_thread;
		   apr_int32_t temp=i;
		   apr_thread_create(&new_thread,NULL,sub_thread_func,&temp,p);
		  // usleep(50000);
                   puts("waiting condition...");
                   //apr_thread_mutex_lock(req_mutex);
                   apr_thread_cond_wait(req_cond, req_mutex);
                   //puts("waiting mutex...");
                   //puts("mutex ok");
                   REQUEST_CNT++;
                   //apr_thread_mutex_lock(mutex);
	  }
          //usleep(5000000);
	  apr_thread_mutex_lock(mutex);
	  apr_thread_cond_timedwait(cond, mutex, 50000000);
          //apr_thread_mutex_unlock(mutex);
          
          apr_thread_t *print_thread;
          apr_thread_create(&print_thread, NULL, print_result, NULL, p);
          apr_thread_mutex_lock(print_mutex);
          apr_thread_cond_timedwait(print_cond, print_mutex, 50000000);

          apr_thread_cond_destroy(print_cond);
          apr_thread_mutex_destroy(print_mutex);
          apr_thread_cond_destroy(cond);
          apr_thread_mutex_destroy(mutex);
          apr_thread_cond_destroy(req_cond);
          apr_thread_mutex_destroy(req_mutex);
		apr_pool_clear(p);
          //return 0;

}
